<?php
/**
 * Copyright (c) 2009, Jacek Karczmarczyk
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * - Neither the name of the author nor the names of its contributors may be
 *   used to endorse or promote products derived from this software without
 *   specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 *
 * PHP Version 5
 *
 * @package Larchana_Db
 * @version 0.1
 * @author Jacek Karczmarczyk <jacek@karczmarczyk.pl>
 */


/**
 * New group of functions/attributed introduced:
 * getFilter($columnName) - returns array of filters for $columnName, uses method getColumnNameFilter()
 * getValidator($columnName) - returns array of validators for $columnName, uses method getColumnNameValidator()
 * getDictionary($columnName) - returns dictionary for $columnName, uses _columnNameDictionary attribute or getColumnNameDictionary() method
 *
 */
class Larchana_Db_Table_Row extends Zend_Db_Table_Row
{
	protected $_errors = array();

	/**
	 * Adds a single error for a column
	 *
	 * @param string $columnName Column name
	 * @param string $error Error string
	 * @throws Zend_Db_Table_Row_Exception if the $columnName is not a column in the row.
	 */
	public function addError($columnName, $error)
	{
        $columnName = $this->_transformColumn($columnName);
        if (!array_key_exists($columnName, $this->_data)) {
            require_once 'Zend/Db/Table/Row/Exception.php';
            throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is not in the row");
        }

		if (!isset($this->_errors[$columnName]))
			$this->_errors[$columnName] = array();

		$this->_errors[$columnName][] = $error;
	}


	/**
	 * Adds a set of errors for a column
	 *
	 * @param string $columnName Column name
	 * @param array  $errors Errors
	 */
	public function addErrors($columnName, array $errors)
	{
		foreach($errors as $error) $this->addError($columnName, $error);
	}


	/**
	 * Clears errors for one or all columns
	 *
	 * @param string|null $columnName Column name, clears all errors if column name is not set
	 * @throws Zend_Db_Table_Row_Exception if the $columnName is not a column in the row.
	 */
	public function clearErrors($columnName = null)
	{
		if (is_null($columnName))
		{
			$this->_errors = array();
			return;
		}

        $columnName = $this->_transformColumn($columnName);
        if (!array_key_exists($columnName, $this->_data)) {
            require_once 'Zend/Db/Table/Row/Exception.php';
            throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is not in the row");
        }
		$this->_errors[$columnName] = array();
	}


	/**
	 * Returns errors for a specified column
	 *
	 * @param string|null $columnName Column name
	 * @return array|string List of errors
	 * @throws Zend_Db_Table_Row_Exception if the $columnName is not a column in the row.
	 */
	public function getErrors($columnName = null)
	{
		if (is_null($columnName)) return $this->_errors;

        $columnName = $this->_transformColumn($columnName);
        if (!array_key_exists($columnName, $this->_data)) {
            require_once 'Zend/Db/Table/Row/Exception.php';
            throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is not in the row");
        }

		return isset($this->_errors[$columnName]) ? $this->_errors[$columnName] : array();
	}


	/**
	 * Row validator based on user validators
	 *
	 * @return boolean True if row values are validated successfully
	 */
	public function isValid()
	{
		$this->clearErrors();
		$isValid = true;
		$columns = $this->getTable()->info('metadata');
		foreach($columns as $columnName=>$column)
		{
			foreach($this->getValidator($columnName) as $validator)
			{
				if (!$validator->isValid($this->$columnName))
				{
					$this->addErrors($columnName, $validator->getMessages());
					$isValid = false;
				}
			}
		}

		return $isValid;
	}


	/**
	 * Returns array of validators for specified column
	 *
	 * @param string $columnName Column name
	 * @return array Array of Zend_Validate_Interface objects
	 */
	public function getValidator($columnName)
	{
        $columnName = $this->_transformColumn($columnName);
		if (!array_key_exists($columnName, $this->_data)) {
			require_once 'Zend/Db/Table/Row/Exception.php';
			throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is not in the row (validator getter)");
		}

		$fn = 'get'.Zend_Filter::filterStatic($columnName, 'Word_UnderscoreToCamelCase').'Validator';
		if (!method_exists($this, $fn)) return array();

		$validators = $this->$fn();
		if (!is_array($validators)) $validators = array($validators);

		foreach($validators as $validator)
			if (!$validator instanceof Zend_Validate_Interface)
				throw new Zend_Db_Exception("Validator for column \"$columnName\" is not instance of Zend_Validate_Interface");
		return $validators;
	}


	/**
	 * Returns array of filters for specified column
	 *
	 * @param string $columnName Column name
	 * @return array Array of Zend_Filter_Interface objects
	 */
	public function getFilter($columnName)
	{
        $columnName = $this->_transformColumn($columnName);
		if (!array_key_exists($columnName, $this->_data)) {
			require_once 'Zend/Db/Table/Row/Exception.php';
			throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is not in the row (filter getter)");
		}

		$fn = 'get'.Zend_Filter::filterStatic($columnName, 'Word_UnderscoreToCamelCase').'Filter';
		if (!method_exists($this, $fn)) return array();

		$filters = $this->$fn();
		if (!is_array($filters)) $filters = array($filters);

		foreach($filters as $filter)
			if (!$filter instanceof Zend_Filter_Interface)
				throw new Zend_Db_Exception("Filter for column \"$columnName\" is not instance of Zend_Filter_Interface");
		return $filters;
	}


	/**
	 * Handler for dictionary method: $row->getDictionary($columnName, $nullValue = null, $flat = false)
	 * - searches for the _columnNameDictionary attribute, if not found
	 * - searches for the _getColumnNameDictionary method, if not found returns null
	 *
	 * @param string      $columnName
	 * @param string|null $nullValue
	 * @param boolean     $flat
	 * @return null|array
	 */
	public function getDictionary($columnName, $nullValue = null, $flat = false)
	{
        $columnName = $this->_transformColumn($columnName);
		if (!array_key_exists($columnName, $this->_data)) {
			require_once 'Zend/Db/Table/Row/Exception.php';
			throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is not in the row");
		}

		// Search for $this->_columnNameDictionary attribute
		//
		$attr = Zend_Filter::filterStatic($columnName, 'Word_UnderscoreToCamelCase');
		$attr[0] = strtolower($attr[0]);
		$attr = "_".$attr."Dictionary";
		if (isset($this->$attr))
			$dictionary = $this->$attr;
		// Search for $this->_getColumnNameDictionary
		//
		else if (method_exists($this, $fn = 'get'.Zend_Filter::filterStatic($columnName, 'Word_UnderscoreToCamelCase').'Dictionary'))
			$dictionary = $this->$fn();
		else
			return null;

		if (!is_array($dictionary))
		{
			require_once 'Zend/Db/Table/Row/Exception.php';
			throw new Zend_Db_Table_Row_Exception("Dictionary for column \"$columnName\" is not an array");
		}

		// Add the "not selected" value
		//
		if (!is_null($nullValue))
		{
			$tmp = array(''=>$nullValue);
			foreach($dictionary as $key=>$value) $tmp[$key] = $value;
			$dictionary = $tmp;
			unset($tmp);
		}

		// Flatten the array
		//
		if ($flat)
		{
			$tmp = array();
			foreach($dictionary as $key=>$value)
				if (is_array($value))
				{
					foreach($value as $subkey=>$subvalue)
						$tmp[$subkey] = $subvalue;
				}
				else
					$tmp[$key] = $value;
			$dictionary = $tmp;
			unset($tmp);
		}

		return $dictionary;
	}


    /**
     * Set row field value (filtered with user filter)
     *
     * @param  string $columnName The column key.
     * @param  mixed  $value      The value for the property.
     */
	public function __set($columnName, $value)
	{
		// Filter value with user filters
		//
		foreach($this->getFilter($columnName) as $filter) $value = $filter->filter($value);

		// Default storing procedure
		//
		parent::__set($columnName, $value);
	}


	/**
	 * Pre-insert login - validates row values before saving
	 *
	 * @throws Zend_Db_Table_Row_Exception if values have incorrect values
	 */
	protected function _insert()
	{
		if ($this->isValid()) return;

		$errors = array();
		foreach($this->getErrors() as $columnErrors) $errors = array_merge($errors, array_values($columnErrors));
		throw new Zend_Exception(implode("\n", $errors));
	}


	/**
	 * Pre-update login - validates row values before saving
	 *
	 * @throws Zend_Db_Table_Row_Exception if values have incorrect values
	 */
	protected function _update()
	{
		if ($this->isValid()) return;

		$errors = array();
		foreach($this->getErrors() as $columnErrors) $errors = array_merge($errors, array_values($columnErrors));
		throw new Zend_Exception(implode("\n", $errors));
	}
}
